#include "StdAfx.h"
#include "mundo.h"
#include "colisionador.h"

Mundo::Mundo()
{
}

Mundo::~Mundo()
{

}
//double getAlto()
//{
//    return alto;
//}
//
//void setAlto(double alto) {
//    this.alto = alto;
//}
//
//double getAncho() {
//    return ancho;
//}
//
//void setAncho(double ancho) {
//    this.ancho = ancho;
//}



//void agregar(List listaEntes)
//{
//    for (Object ente : listaEntes) {
//        agregar(ente);
//    }
//}

void Mundo::agregar(Bola* bola)
{
    entesMoviles.append(bola);
    entes.append(bola);
}

void Mundo::agregar(Arista* arista)
{
    entes.append(arista);
}

//void eliminar(Ente* ente)
//{
//    entes.remove(ente);
//}

//void visitarEntesMoviles(VisitadorEntesMoviles visitador)
//{
//    for (EnteMovil enteMovil : entesMoviles) {
//        visitador.visitar(enteMovil);
//    }
//}

bool Mundo::esIntrusista(Bola* enteMovil)
{
    if (enteMovil->permiteIntrusion()) {
        return false;
    }

    foreach (Ente* ente, entes) {
        if (ente == enteMovil || ente->permiteIntrusion()) {
            continue;
        }
        double separacion = enteMovil->calcularSeparacion(ente);
        if (!fequals(separacion, 0) && separacion < 0) {
            //System.out.println("Intrusismo: " + enteMovil + " con " + ente);
            return true;
        }
    }
    return false;
}

//void pintar(Graphics2D graphics) {
//
//    for (Ente ente : entes) {
//        ente.pintar(graphics);
//    }
//
//    for (Colision colision : colisionesEjecutadas) {
//        colision.pintar(graphics);
//    }
//}

void Mundo::comprobarMovimientoEnte(Bola* enteMovil)
{
    if (enteMovil == NULL) {
        return;
    }

    if (!entesMovilesEnMovimiento.contains(enteMovil)) {
        if (!enteMovil->haCompletadoMovimiento()) {
            entesMovilesEnMovimiento.append(enteMovil);
        }
    } else if (enteMovil->haCompletadoMovimiento()) {
        entesMovilesEnMovimiento.removeOne(enteMovil);
    }
}

void Mundo::mover()
{
    aplicarFuerzasExternas();

    // Se separan en una lista los entes moviles que se esten moviendo
    foreach (Bola* enteMovil, entesMoviles) {
        if (enteMovil->prepararMovimiento()) {
            entesMovilesEnMovimiento.append(enteMovil);
        }
    }

    colisionesEjecutadas.clear();
//        System.out.println("----movimiento--------------------------------------------");
    while (!entesMovilesEnMovimiento.isEmpty()) {
        Bola* enteMovil = entesMovilesEnMovimiento.at(0);
        entesMovilesEnMovimiento.removeOne(enteMovil);
        Colision colision = calcularColision(enteMovil);
        if (colision.esValida()) {
            colision.ejecutar();
            colisionesEjecutadas.append(colision);
            comprobarMovimientoEnte(colision.getEntePasivo());
            comprobarMovimientoEnte(colision.getEnteActivo());                
        } else {
            enteMovil->mover();
//                System.out.println("movimiento " + enteMovil);
        }
        comprobarIntrusismo(enteMovil);
        
    }
}

void Mundo::comprobarIntrusismo(Bola* enteMovil)
{
    if (esIntrusista(enteMovil)) {
        enteMovil->deshacerMovimiento();
    }
}

void Mundo::aplicarFuerzasExternas()
{
    foreach (Bola* enteMovil, entesMoviles) {
        Vector2d sumaResultantes;
        foreach (FuerzaExterna* fuerza, fuerzas) {
            Vector2d vector = fuerza->getVectorFuerza(enteMovil);

            if (vector.esValido() && !vector.esNulo()) {
                if (fuerza->isAnularResto()) {
                    enteMovil->getVelocidad().setModulo(0);
                    sumaResultantes = vector;
                    break;
                } else {
                    sumaResultantes.sumar(vector);
                }
            }
        }
        enteMovil->aplicarFuerza(sumaResultantes);

        aplicarFuerzasPlanoInclinado(enteMovil);
    }
}

void Mundo::limpiar()
{
    entes.clear();
    entesMoviles.clear();    
    fuerzas.clear();
    colisionesEjecutadas.clear();
}

/*!
 * Calcula la colision que ocurre primero (menos distancia de colision)
 */
Colision Mundo::calcularColision(Bola* enteActivo)
{
    if (enteActivo->getVelocidad().esNulo() ||
        fequals(enteActivo->getProporcionDesplazamientoPendiente(), 0)) {
        return Colision();
    }

    Colision colision;
    foreach (Ente* ente, entes) {
        if (ente == enteActivo) {
            continue;
        }
        Colision colisionCandidata = Colisionador::calcularColision(enteActivo, ente);
        if (!colisionCandidata.esValida()) {
            continue;
        }
        if (!colision.esValida() || colision.getProporcionDesplazamientoRealizado() > colisionCandidata.getProporcionDesplazamientoRealizado()) {
            colision = colisionCandidata;
        }
    }
    return colision;
}

void Mundo::aplicarFuerzasPlanoInclinado(Bola* enteMovil)
{
    QList<Ente*> entesExcluidos;
    entesExcluidos.append(enteMovil);
    entesExcluidos.append(NULL);

    foreach (Ente* ente, entes) {
        if (ente == enteMovil) {
            continue;
        }

        Vector2d fuerza = enteMovil->calcularFuerzaPlanoInclinado(ente);
        if (fuerza.esValido()) {
            Bola copiaEnteMovil(*enteMovil);
            copiaEnteMovil.aplicarFuerza(fuerza);
            entesExcluidos[1] = ente;
            Vector2d otraFuerza = calcularFuerzasPlanoInclinado(&copiaEnteMovil, entesExcluidos);
            // Si no hay mas fuerzas de plano inclinado que afecten al movimiento resultante, se aplica la fuerza
            if (!otraFuerza.esValido()) {
                enteMovil->aplicarFuerza(fuerza);
                break;
            }
        }
    }
}

Vector2d Mundo::calcularFuerzasPlanoInclinado(Bola* enteMovil, QList<Ente*> entesExcluidos)
{
    foreach (Ente* ente, entes) {
        if (entesExcluidos.contains(ente)) {
            continue;
        }
        Vector2d fuerza = enteMovil->calcularFuerzaPlanoInclinado(ente);
        if (fuerza.esValido()) {
            return fuerza;
        }
    }
    return Vector2d::getVectorInvalido();
}
